﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace klient {
    class Client {
        /// <summary>
        /// Adres IP serwera, z którym łączy się klient.
        /// </summary>
        public string IpAddress { 
            get { 
                return ipAddress; 
            } 
        }
        /// <summary>
        /// Port, z którym nawiązywane jest połączenie.
        /// </summary>
        public int Port { 
            get { 
                return port; 
            } 
        }
        /// <summary>
        /// True, gdy klient jest połączony z serwerem, false w przeciwnym wypadku.
        /// </summary>
        public bool IsConnected { 
            get {
                if (socket == null) return false;
                return socket.Connected; 
            } 
        }
        /// <summary>
        /// Socket używany do komunikacji z serwerem.
        /// </summary>
        private Socket socket;
        /// <summary>
        /// Port, z którym nawiązywane jest połączenie.
        /// </summary>
        private int port;
        /// <summary>
        /// Adres IP serwera, z którym łączy się klient.
        /// </summary>
        private string ipAddress;

        /// <summary>
        /// Tworzy instancję obiektu wysyłającego dane do serwera i odbierającego odpowiedzi.
        /// </summary>
        /// <param name="ipAddress">Adres IP serwera.</param>
        /// <param name="port">Port serwera.</param>
        public Client(string ipAddress, int port) {
            this.ipAddress = ipAddress;
            this.port = port;
        }
        /// <summary>
        /// Nawiązuje połączenie z serwerem.
        /// </summary>
        /// <returns>True w przypadku sukcesu, false gdy połączenie jest już nawiązane.</returns>
        public bool Connect() {
            if (IsConnected) return false;
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ep = new IPEndPoint(IPAddress.Parse(ipAddress), port);
            try {
                socket.Connect(ep);
            }
            catch (SocketException ex) {
                throw new Exception("Nie można połączyć się z serwerem o podanym adresie na podanym porcie.", ex);
            }
            return true;
        }
        /// <summary>
        /// Zamyka połączenie z serwerem.
        /// </summary>
        /// <returns>True w przypadku sukcesu, false gdy połączenie jest już zamknięte.</returns>
        public bool Disconnect() {
            if (!IsConnected) return false;
            socket.Close(100);
            return true;
        }
        /// <summary>
        /// Wysyła obiekt do serwera.
        /// </summary>
        /// <param name="data">Obiekt do wysłania.</param>
        public void Send(object data) {
            if(data == null) throw new ArgumentNullException("Nie można wysłać NULLa.");
            if (!IsConnected) throw new InvalidOperationException("Brak połączenia z serwerem.");
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter 
                = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            System.IO.MemoryStream stream = new System.IO.MemoryStream();
            formatter.Serialize(stream, data);
            byte[] serializedMessage = stream.ToArray();
            try {
                socket.Send(serializedMessage);
            }
            catch (SocketException ex) {
                throw ex;
            }
        }
        /// <summary>
        /// Odbiera dane od serwera, w razie potrzeby czekając na pojawienie się ich w buforze.
        /// </summary>
        /// <returns>Dane otrzymane od serwera.</returns>
        public object Receive() {
            if (!IsConnected) throw new InvalidOperationException("Brak połączenia z serwerem.");
            const int bufferSize = 4096;
            int byteCount = 0;
            List<byte[]> bufferList = new List<byte[]>();
            try {
                do {
                    byte[] buffer = new byte[bufferSize];                    
                    byteCount += socket.Receive(buffer);
                    if (byteCount == 0) throw new SocketException();
                    bufferList.Add(buffer);
                    if (socket.Available == 0)
                        System.Threading.Thread.Sleep(100);
                } while (socket.Available != 0);
                byte[] message = new byte[byteCount];
                int offset = 0;
                foreach (byte[] b in bufferList) {
                    int count = byteCount > bufferSize ? bufferSize : byteCount;
                    Buffer.BlockCopy(b, 0, message, offset, count);
                    offset += bufferSize;
                }
                //serializacja:
                System.IO.MemoryStream messageMemoryStream = new System.IO.MemoryStream(message);
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter
                        = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                messageMemoryStream.Position = 0;
    	        return formatter.Deserialize(messageMemoryStream);
            }
            catch (SocketException ex) {
                throw ex;
            }
        }

        /// <summary>
        /// Kończy działanie programu.
        /// </summary>
        public void Exit() {
            Disconnect();
            throw new Exception("Polecenie exit");
        }
    }
}
